/*
 * Copyright 2010-2012 Susanta Tewari. <freecode4susant@users.sourceforge.net>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package genomemap.worker;

import genomemap.data.DNA_RegionUtil;
import genomemap.data.DataSource;
import genomemap.data.JDataSource;
import genomemap.data.RFLPImputation;

import gui.genomemap.event.event.WorkerOutputEvent;
import gui.genomemap.inputModel.AnnealIModel;
import gui.genomemap.inputModel.ProgCurveIModel;
import gui.genomemap.outputModel.AnnealOModel;
import gui.genomemap.view.inputView.ProgCurveIView;

import javautil.collections.ArrayUtil;

import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author stewari1
 */
public class ProgCurve extends Worker {

    /** Field description */
    private int ch_id = -1;

    /** Field description */
    private int annealCriterion = 0;

    /** Field description */
    private int annealMachineType = 0;

    /** Field description */
    private Annealable annealable = null;

    /** Field description */
    private AnnealMachine annealMachine = null;

    /** Field description */
    private Set<AnnealIModel> annealIModelSet = null;

    /** Field description */
    private final Set<AnnealOModel> annealOModelSet = new LinkedHashSet<>();

    /**
     * verbose LOGGING
     */
    private boolean verbose = false;

    /**
     * Constructs ...
     */
    public ProgCurve() {}

    /**
     * Constructs ...
     *
     * @param inputModel description
     */
    public ProgCurve(ProgCurveIModel inputModel) {

        ch_id             = inputModel.getLinkageGroup();
        annealCriterion   = inputModel.getAnnealCriterion();
        annealMachineType = inputModel.getAnnealMachineType();
        annealIModelSet   = inputModel.getAnnealIModelSet();


        // set up the proper annelable and anneal machine
        AnnealIModel sampleAnnealIModel = inputModel.getAnnealInputOptions();

        if (annealCriterion == Annealable.ANNEAL_CRITERION_PHOOD) {

            PLikeliHood pHood = new PLikeliHood();

            pHood.setPData(DataSource.getPDataSource(inputModel.getLinkageGroup()));

            annealable = pHood;

        } else if (annealCriterion == Annealable.ANNEAL_CRITERION_JHOOD) {

            JLikeliHood jHood = new JLikeliHood();

            jHood.setGIterationLimit(sampleAnnealIModel.getGIterationLimit());
            jHood.setGConvgEpsilon(sampleAnnealIModel.getGConvgEpsilon());

            JDataSource jData = DataSource.getJDataSource(sampleAnnealIModel.getLinkageGroup());

            jData.getGData().setRflpImputation(sampleAnnealIModel.getRflpImputation());
            jHood.setJData(jData);

            annealable = jHood;

        } else if (annealCriterion == Annealable.ANNEAL_CRITERION_HAM_DIST) {


            // 2nd arg -0 for ordering probes
            annealable = new HammingDistance(sampleAnnealIModel.getLinkageGroup(), 0);
        }


        // set up the proper anneal machine
        if (annealMachineType == AnnealMachine.MACHINE_TYPE_SIMULATED_ANNEALING) {
            annealMachine = new SimulatedAnnealing(sampleAnnealIModel);
        }

        if (annealMachineType == AnnealMachine.MACHINE_TYPE_RANDOM_COST) {
            annealMachine = new RandomCost();
        }

        annealMachine.setAnnealable(annealable);
    }

    public static void main(String[] args) {


        // create ProgCurveIModel
        ProgCurveIModel inputModel = new ProgCurveIModel();

        inputModel.setLinkageGroup(1);
        inputModel.setSegProbeCount(10);

        int[] order = {

            1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
            25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46,
            47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68,
            69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90,
            91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109,
            110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126,
            127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143,
            144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160,
            161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177,
            178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194,
            195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211,
            212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228,
            229, 230, 231, 232, 233, 234, 235, 236, 237
        };

        inputModel.setOrder(order);
        inputModel.setSegmentCount(order.length / inputModel.getSegProbeCount());
        inputModel.setAnnealCriterion(Annealable.ANNEAL_CRITERION_HAM_DIST);
        inputModel.setAnnealMachineType(AnnealMachine.MACHINE_TYPE_RANDOM_COST);

        AnnealIModel annealIModel = new AnnealIModel();

        inputModel.setAnnealInputOptions(annealIModel);


        // set the anneal options
        annealIModel.setGIterationLimit(1);
        annealIModel.setGConvgEpsilon(0.0001);

        RFLPImputation rflpImputation = new RFLPImputation();

        rflpImputation.setPolicyIndex(RFLPImputation.POLICY_PROB);
        rflpImputation.setImputationProb(0.5);
        annealIModel.setRflpImputation(rflpImputation);
        annealIModel.setMASTER_ITER_COUNT(500000);
        annealIModel.setTEMP_FRICTION(0.95);
        annealIModel.setTEMP_ITER_COUNT(40000);
        annealIModel.setSTART_TEMP(100.0);
        annealIModel.setMAX_UNBEATEN_COUNT(10000);
        annealIModel.setMAX_BEATEN_COUNT(2000);
        annealIModel.setPercent(0.001);
        annealIModel.setLinkageGroup(inputModel.getLinkageGroup());

        Integer[] entireProbeOrder = ArrayUtil.getIntegerArray(inputModel.getOrder());
        int segmentLen             = inputModel.getSegProbeCount();
        int segments               = inputModel.getSegmentCount();

        try {

            for (int segment = 1; segment <= segments - 1; segment++) {

                int endPos                       = segment * segmentLen;
                int[] segmentOrder =
                    ArrayUtil.getIntegerArray(ArrayUtil.getSubArray(entireProbeOrder,
                        Integer.class, 1, endPos));
                AnnealIModel segmentAnnealIModel = (AnnealIModel) annealIModel.clone();

                segmentAnnealIModel.setOrder(segmentOrder);
                inputModel.addAnnealIModel(segmentAnnealIModel);
            }


            // the last segment
            int endPos                       = entireProbeOrder.length;
            int[] segmentOrder = ArrayUtil.getIntegerArray(ArrayUtil.getSubArray(entireProbeOrder,
                                     Integer.class, 1, endPos));
            AnnealIModel segmentAnnealIModel = (AnnealIModel) annealIModel.clone();

            segmentAnnealIModel.setOrder(segmentOrder);
            inputModel.addAnnealIModel(segmentAnnealIModel);

        } catch (CloneNotSupportedException ex) {
            Logger.getLogger(ProgCurveIView.class.getName()).log(Level.SEVERE, null, ex);
        }

        ProgCurve progCurve = new ProgCurve(inputModel);

        progCurve.setVerbose(true);

        try {
            progCurve.run();
        } catch (Exception exception) {
            exception.printStackTrace();
        }
    }

    public void run() {

        setStatusInfo("Progress Curve Computation has Started");
        annealMachine.setParentWorker(this);    // listeners are guaranteed to have set before run()


        // get the hybrid data that will used for contig counts.
        // Optimizing: so that hybrid data doesn`t have to be created at each iteration.
        // needed for probe contig count:
        // int[][] hybridData = DataSource.getPDataSource(ch_id).getHybridData();
        Iterator<AnnealIModel> itr = annealIModelSet.iterator();

        while (itr.hasNext()) {

            fireWorkerOutputEvent(new WorkerOutputEvent(this, WorkerOutputEvent.PROG_CURVE));

            AnnealIModel annealIModel = itr.next();


            // set the initial order using random cost for selected cases
            if ((annealMachineType == AnnealMachine.MACHINE_TYPE_SIMULATED_ANNEALING)
                    && (annealCriterion != Annealable.ANNEAL_CRITERION_HAM_DIST)) {

                RandomCost randomCost = new RandomCost();
                HammingDistance hd    = new HammingDistance(annealIModel.getLinkageGroup(), 0);

                hd.setOrder(annealIModel.getOrder());
                randomCost.setIncrAnnealable(hd);
                randomCost.setLogEnabled(false);
                setStatusInfo("Initializing with Random Cost");
                randomCost.run();
                annealable.setOrder(randomCost.getConvOrder());

            } else {
                annealable.setOrder(annealIModel.getOrder());
            }

            annealable.setOrder(annealIModel.getOrder());
            annealMachine.run();


            // collect the output model
            AnnealOModel annealOModel = new AnnealOModel();

            annealOModel.setInputModel(annealIModel);
            annealOModel.setConvValue(annealMachine.getConvValue());
            annealOModel.setOrder(annealMachine.getConvOrder());


            // based on converged probe order.
            int contigCount = DNA_RegionUtil.getProbeContigCount(ch_id, annealOModel.getOrder());

            System.out.println("Probe Count: " + annealOModel.getOrder().length + " Contig Count: "
                               + contigCount);
            annealOModel.setContigCount(contigCount);
            getAnnealOModelSet().add(annealOModel);
        }

        setStatusInfo("Progress Curve Computation has Finished");
        setShowProgress(false);
    }

    public void setAnnealIModelSet(Set<AnnealIModel> annealIModelSet) {
        this.annealIModelSet = annealIModelSet;
    }

    public Set<AnnealOModel> getAnnealOModelSet() {
        return annealOModelSet;
    }

    public boolean isVerbose() {
        return verbose;
    }

    void setVerbose(boolean verbose) {
        this.verbose = verbose;
    }
}
